// I2C主控制器测试平台
// 版本：V1.2
// 作者：杨纯一
// 日期：2025-09-25

`timescale 10ns / 1ps

module i2c_tb;
    
    // 系统信号
    reg clk;
    reg reset;
    
    // 控制信号
    reg i_req;
    reg i_rw;
    reg [6:0] i_dev_addr;
    reg [7:0] i_data;
    wire [7:0] o_data;
    wire o_busy;
    wire o_ack_error;
    
    // I2C总线信号
    wire sda;
    wire scl;
    
    // 从设备模型信号
    reg [7:0] slave_data;
    reg slave_ack;
    
    // 实例化DUT
    i2c_master #(
        .SYS_CLOCK(20_000_000),
        .SCL_CLOCK(100_000)
    ) dut (
        .clk(clk),
        .reset(reset),
        .i_req(i_req),
        .i_rw(i_rw),
        .i_dev_addr(i_dev_addr),
        .i_data(i_data),
        .o_data(o_data),
        .o_busy(o_busy),
        .o_ack_error(o_ack_error),
        .sda(sda),
        .scl(scl)
    );
    
    // 简单的I2C从设备模型
    reg sda_slave;
    reg sda_slave_oe;
    reg [7:0] received_data;
    reg [2:0] bit_count_slave;
    reg [1:0] slave_state;
    reg [7:0] tx_data;
    
    // 从设备状态机
    localparam SLAVE_IDLE = 2'b00;
    localparam SLAVE_ADDR = 2'b01;
    localparam SLAVE_DATA = 2'b10;
    localparam SLAVE_ACK  = 2'b11;
    
    // 初始化从设备
    initial begin
        sda_slave_oe = 0;
        sda_slave = 1;
        received_data = 0;
        bit_count_slave = 7;
        slave_state = SLAVE_IDLE;
        tx_data = 8'h55; // 测试数据
        slave_ack = 1;
    end
    
    // 从设备响应逻辑
    always @(negedge scl) begin
        case (slave_state)
            SLAVE_IDLE:
                if (sda == 0) begin // 检测起始条件
                    bit_count_slave = 7;
                    slave_state = SLAVE_ADDR;
                end
                
            SLAVE_ADDR:
                begin
                    received_data[bit_count_slave] = sda;
                    if (bit_count_slave == 0) begin
                        bit_count_slave = 7;
                        slave_state = SLAVE_ACK;
                        // 检查地址是否匹配
                        if (received_data[7:1] == 7'h30) begin
                            slave_ack = 0;
                            // 检查读写位
                            if (received_data[0] == 1) begin
                                // 读操作，准备发送数据
                                tx_data = 8'hA5;
                            end
                        end else begin
                            slave_ack = 1;
                        end
                    end else begin
                        bit_count_slave = bit_count_slave - 1;
                    end
                end
                
            SLAVE_DATA:
                begin
                    received_data[bit_count_slave] = sda;
                    if (bit_count_slave == 0) begin
                        bit_count_slave = 7;
                        slave_state = SLAVE_ACK;
                        $display("从设备接收到数据: %h", received_data);
                    end else begin
                        bit_count_slave = bit_count_slave - 1;
                    end
                end
                
            SLAVE_ACK:
                begin
                    if (received_data[0] == 0) begin // 写操作
                        slave_state = SLAVE_DATA;
                    end else begin // 读操作
                        // 准备发送数据
                        sda_slave_oe = 1;
                        sda_slave = tx_data[7]; // 发送最高位
                        tx_data = {tx_data[6:0], 1'b0};
                        bit_count_slave = 6;
                        slave_state = SLAVE_DATA;
                    end
                end
        endcase
    end
    
    // 从设备数据发送（读操作）
    always @(negedge scl) begin
        if (slave_state == SLAVE_DATA && received_data[0] == 1 && bit_count_slave >= 0) begin
            sda_slave = tx_data[bit_count_slave];
            if (bit_count_slave == 0) begin
                sda_slave_oe = 0;
            end
        end
    end
    
    // 从设备应答信号
    always @(posedge scl) begin
        if (slave_state == SLAVE_ACK) begin
            sda_slave_oe = 1;
            sda_slave = slave_ack;
        end
    end
    
    // 停止条件检测
    always @(posedge scl) begin
        if (sda == 1 && sda_slave_oe == 0) begin
            slave_state = SLAVE_IDLE;
            sda_slave_oe = 0;
        end
    end
    
    // 三态总线连接
    assign sda = sda_slave_oe ? ((sda_slave == 0) ? 1'b0 : 1'bz) : 1'bz;
    
    // 时钟生成
    initial begin
        clk = 0;
        forever #25 clk = ~clk; // 20MHz时钟
    end
    
    // 复位
    initial begin
        reset = 1;
        #100 reset = 0;
    end
    
    // 测试激励
    initial begin
        // 初始化信号
        i_req = 0;
        i_rw = 0;
        i_dev_addr = 7'h30;
        i_data = 8'h00;
        
        #200;
        
        // 测试单字节写操作
        i_data = 8'hAA;
        i_req = 1;
        wait(o_busy == 1);
        i_req = 0;
        wait(o_busy == 0);
        $display("写操作完成，o_ack_error = %b", o_ack_error);
        
        #500;
        
        // 测试单字节读操作
        i_rw = 1;
        i_req = 1;
        wait(o_busy == 1);
        i_req = 0;
        wait(o_busy == 0);
        $display("读操作完成，读取到数据: %h, o_ack_error = %b", o_data, o_ack_error);
        
        #500;
        
        // 测试错误场景 - 地址不匹配
        i_dev_addr = 7'h40;
        i_rw = 0;
        i_data = 8'hBB;
        i_req = 1;
        wait(o_busy == 1);
        i_req = 0;
        wait(o_busy == 0);
        $display("错误地址测试，o_ack_error = %b", o_ack_error);
        
        #500;
        
        // 结束仿真
        $finish;
    end
    
    // 监控I2C总线信号
    initial begin
        $monitor("时间: %t, scl: %b, sda: %b, 状态: %h, o_busy: %b, o_ack_error: %b", 
                 $time, scl, sda, dut.state, o_busy, o_ack_error);
    end
    
    // 生成VCD文件用于波形查看
    initial begin
        $dumpfile("i2c_tb.vcd");
        $dumpvars(0, clk);
        $dumpvars(0, reset);
        $dumpvars(0, i_req);
        $dumpvars(0, i_rw);
        $dumpvars(0, i_dev_addr);
        $dumpvars(0, i_data);
        $dumpvars(0, o_data);
        $dumpvars(0, o_busy);
        $dumpvars(0, o_ack_error);
        $dumpvars(0, sda);
        $dumpvars(0, scl);
        $dumpvars(0, dut.state); 
    end
    
endmodule